package ss.pku.jsa.type.inference;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ContinueStatement;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.LabeledStatement;
import org.eclipse.jdt.core.dom.SimpleName;

@SuppressWarnings({ "rawtypes" })
public class TypeClosureVisitor extends ASTVisitor {
	Analysis analysis = null;
	CompilationUnit compilationUnit;
	String filename;
	Set searchedSet = new HashSet();

	public TypeClosureVisitor(Analysis analysis,
			CompilationUnit compilationUnit, String filename) {
		this.analysis = analysis;
		this.compilationUnit = compilationUnit;
		this.filename = filename;
	}

	public void preVisit(ASTNode node) {
	}

	public void postVisit(ASTNode node) {
	}

	public boolean visit(BreakStatement node) {
		return false;
	}

	public boolean visit(ContinueStatement node) {
		return false;
	}

	public boolean visit(ImportDeclaration node) {
		return false;
	}

	public void endVisit(SimpleName node) {
		if ((node.getParent() instanceof LabeledStatement)) {
			return;
		}
		IBinding b = node.resolveBinding();
		Location l = getLocation(node);

		if (b == null) {
			this.analysis.logMissingClass(node.toString(), l);
		} else if ((b instanceof ITypeBinding)) {
			ITypeBinding t = (ITypeBinding) b;
			processType(t, l);
		}
	}

	private void processType(ITypeBinding t, Location l) {
		if (((!t.isClass()) && (!t.isInterface())) || (this.analysis.seen(t))) {
			return;
		}
		this.analysis.markSeen(t);

		this.analysis.addTypeFields(t);

		ICompilationUnit c = findType(t);

		if (c != null) {
			if (!this.analysis.seen(c)) {
				this.analysis.addToVisitList(c);
			}
		} else {
			TypeKey key = TypeKey.getTypeKey(t);
			this.analysis.typesUsed.add(key);
			String name = this.analysis.createName(t);
			ClassQType qt = this.analysis.lookupClassQType(key);
			if (qt == null) {
				Profile.logMemStart("processType/new ClassQType");
				qt = new ClassQType(t, l, name.toUpperCase(), false);
				Profile.logMemEnd();
				this.analysis.addClassQType(key, qt);
			} else {
				qt.l = l;
			}

		}

		int length = t.getInterfaces().length;
		if (t.getSuperclass() != null)
			length++;
		ITypeBinding[] supertypes = new ITypeBinding[length];
		System.arraycopy(t.getInterfaces(), 0, supertypes, 0,
				t.getInterfaces().length);
		if (t.getSuperclass() != null)
			supertypes[(length - 1)] = t.getSuperclass();
		for (ITypeBinding st : supertypes)
			processType(st, l);
	}

	public ICompilationUnit findType(ITypeBinding t) {
		IType result = null;
		try {
			Iterator p = this.analysis.projects.iterator();
			do {
				IJavaProject j = (IJavaProject) p.next();
				result = j.findType(t.getQualifiedName());

				if ((result != null) && (result.getCompilationUnit() == null))
					result = null;
				if (result != null)
					break;
			} while (p.hasNext());
		} catch (JavaModelException e) {
			e.printStackTrace();
			throw new RuntimeException("JavaModelException in findType");
		}
		if (result != null) {
			return result.getCompilationUnit();
		}
		return null;
	}

	private Location getLocation(ASTNode node) {
		return Location.getLocation(node, this.compilationUnit, this.filename);
	}
}
